<template>
    <!-- <input v-model="text" type="text"> -->
    <div>{{ text }}</div>
    <button @click="changeRef">ref</button>
    <div>{{ obj.name }}</div>
    <button @click="changeShallowRef">shallowRef / triggerRef</button>
    <div>{{ msg }}</div>
    <button @click="changeCustomRef">customRef</button>

    <div>reactive arr{{ arr }}</div>
    <div>reactive obj{{ obj2 }}</div>

    <div>readonly copy{{ copy }}</div>

    <br>

    <button @click="changeTest">外层</button>
    <button @click="changeName">里层</button>
    <div>shallowReactive 外层{{ obj3.test }}</div>
    <div>shallowReactive 里层{{ obj3.obj.name }}</div>

    <br>

    <button @click="changeToRef">改变toRef</button>
    <div>toRef {{ state }}</div>

    <br>

    <button @click="changeToRefs">改变toRefs</button>
    <div>toRefs-foo--> {{ foo }}</div>
    <div>toRefs-bar--> {{ bar }}</div>

    <br>
    computed 计算属性
    <div>
        <input
            v-model="testComputed1"
            :oninput="testComputed1=testComputed1.match(/^\d*(\.?\d*)/g)[0].replace(/^(0+)/g,'')"
            type="text"
        />
    </div>
    <input
        v-model="testComputed2"
        :oninput="testComputed2=testComputed2.match(/^\d*(\.?\d*)/g)[0].replace(/^(0+)/g,'')"
        type="text"
    />
    <div>{{ testComputed1 || 0 }} + {{ testComputed2 || 0 }} = {{ testAdd1 }}</div>
    <div>{{ testComputed1 || 0 }} - {{ testComputed2 || 0 }} = {{ testAdd2 }}</div>
    <br>
    <div>案例</div>
    <table border style="width: 800px; text-align: center;">
        <thead>
            <tr>
                <th>名称</th>
                <th>数量</th>
                <th>价格</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
            <tr
                v-for="(item, index) in shopList"
                :key="index"
            >
                <td v-text="item.name"></td>
                <td>
                    <button @click="addAndSub(index, item, false)">-</button>
                    <span v-text="item.num"></span>
                    <button @click="addAndSub(index, item, true)">+</button>
                </td>
                <td v-text="item.price"></td>
                <td>
                    <button @click="deleteItem(index)">删除</button>
                </td>
            </tr>
        </tbody>
        <tfoot>
            <tr>
                <td></td>
                <td></td>
                <td></td>
                <td>总价：{{ allPrice }}</td>
            </tr>
        </tfoot>
    </table>

    <br>
    <div>watch 监听</div>
    <input v-model="watchValue" type="text">
    <input v-model="watchValue1" type="text">
    <input v-model="watchObj.text.name" type="text">

    <br>
    <div>watchEffect 高级监听</div>
    <input id="ipt" v-model="watchEffectValue1" type="text">
    <input v-model="watchEffectValue2" type="text">
    <button @click="stopWatchEffect">停止监听</button>

    <br>
    <button @click="changeFlag">组件的开关</button>
    <HelloWorld v-if="flag" :msg="'haha'"></HelloWorld>
</template>

<script setup lang="ts">
import {
    ref,
    Ref,
    isRef,
    shallowRef,
    triggerRef,
    customRef,
    reactive,
    readonly,
    shallowReactive,
    toRef,
    toRefs,
    toRaw,
    computed,
    watch,
    watchEffect,
} from 'vue'

import HelloWorld from './components/HelloWorld.vue' // 引入组件

// 3 月 11 日总结 进度
// 1.完善整体架构的搭建
// 2.学习vue3的特性
// （1）重写了双向绑定。Object.defineProperty() 替换成立性能更高的Prox
// （2）提升了VDOM性能，优化了diff算法，以前是对比全量， 现在是对比带有patch flag标记的
// （3）Fragment 允许template里写多个节点， 支持render JSX的写法
// （4）Tree shaking，让全局的api分块，没有用过的功能不会包含在基础包中
// （5）引入了composition Api setup 函数时编程 ref reactive watch computed toRefs toRaws。
// 3. 学习组合式Api 中的 ref reactive
// ref: ref shallowRef(不改变) triggerRef(配合shallowRef强制更新) customRef（自定义ref， trank(收集), trigger(更新)）
// reactive: reactive(接受数组或对象) readonly(只读) shallowReactive(浅层次改变)

let text:Ref<string> = ref('test') // 1 .ref 响应式
const changeRef = () => {
    text.value = 'changetest'
}

let obj = shallowRef({ // 2. shallowRef 不变
    name: '喜喜',
})
const changeShallowRef = () => {
    obj.value.name = 'haha'
    triggerRef(obj) // 3. triggerRef 强制变化
}

let notRef = 1
console.log(isRef(notRef)) // 4. isRef 检测 是否为ref
console.log(isRef(text))

function MyRef<T>(value:T) {
    return customRef((trank, trigger) => ({ // 5. customRef 自定义ref
        get() {
            trank() // 收集依赖
            return value
        },
        set(newVal:T) {
            console.log('set')
            value = newVal
            trigger() // 更新
        },
    }))
}
let msg = MyRef<string>('喜喜')
const changeCustomRef = () => {
    msg.value = 'haha'
}

type O = {
    list:number[]
}
let arr = reactive<O>({ // 6. reactive 只接受 Object 和 Array
    list: [],
})
let obj2 = reactive({
    name: 'xia',
})
obj2.name = 'aix'
setTimeout(() => { // 异步改变 1 结构push赋值， 2. 对象里面的数组
    arr.list = [1, 2, 3, 4, 5]
}, 1000)

let copy = readonly(obj2) // 7. readonly 拷贝 proxy 对象 设置为只读
// copy.name = '你好'

let obj3 = shallowReactive({ // 8. shallowReactive 浅层次的改变， 深不变， 注意：只有异步操作不会改变
    test: '你好测试',
    obj: {
        name: 'ooo',
    },
})
const changeTest = () => {
    obj3.test = '改变测试'
}
const changeName = () => {
    obj3.obj.name = '改变name'
}

const toRefObj = {
    foo: 1,
    bar: 1,
}
const state = toRef(toRefObj, 'bar') // 9. toRef 对自身和原始对象造成影响 对页面试图无影响， 如果是reactive 则页面视图会变

const changeToRef = () => {
    state.value++

    console.log('---> 原始对象', toRefObj)
    console.log('---> toRef对象', state.value)
}

let objToRefs = reactive({
    foo: 1,
    bar: 1,
})

let { foo, bar } = toRefs(objToRefs) // 10. toRefs 解构赋值为响应式， 底层是调用toRef方法
const changeToRefs = () => {
    foo.value++
    bar.value++
}
console.log(foo.value, bar.value)

const toRawObj = reactive({
    foo: 1,
    bar: 1,
})
console.log(toRawObj)
const rawObj = toRaw(toRawObj) // 11. toRaw 将响应式恢复成原始数据
console.log(rawObj)

let testComputed1:any = ref('')
let testComputed2:any = ref('')

// 写法1
const testAdd1 = computed(() => parseInt(testComputed1.value || 0) + parseInt(testComputed2.value || 0))

// 写法2
const testAdd2 = computed({
    get() {
        return parseInt(testComputed1.value || 0) - parseInt(testComputed2.value || 0)
    },
    set() {
        parseInt(testComputed1.value || 0) - parseInt(testComputed2.value || 0)
    },
})

// 实践 购物车案例
type Shop = {
    name: string,
    num: number,
    price: number
}
const shopList = reactive<Shop[]>([])
for (let i = 1; i < 10; i++) {
    shopList.push({
        name: `商品:${i}`,
        num: 1,
        price: i * 100,
    })
}
const allPrice = computed<number>(() => {
    let all = shopList.reduce((total: number, item: Shop) => {
        total += item.num * item.price
        return total
    }, 0)
    return all
})
const addAndSub = (index: number, item: Shop, type: boolean) => {
    if (type) { 
        if (item.num >= 99) { return }
        return item.num++ 
    }
    if (item.num == 0) { return }
    item.num--
}
const deleteItem = (index: number) => {
    shopList.splice(index, 1)
}

// watch 写法
let watchValue = ref<string>('')
let watchValue1 = ref<string>('')

let watchObj = reactive<any>({ // ref 需要deep触发深层  reactive 可以直接触发
    name: 1,
    text: {
        name: 2,
    },
})

//  [] 数组监听多个， () => watchObj.name 监听一个
watch(watchObj, (newVal:any, oldVal:any) => { // 函数监听单一
    console.log(newVal.text.name, oldVal)
}, {
    // deep: true,
    immediate: true,
})

// 2022/3/14 总结
// 1. 学习了toRef(数据变， 页面视图不变)， toRefs (用于结构赋值的响应) ,toRaw(恢复原始数值)
// 2. computed 学习计算属性的vue3的使用以及小案例
// 3. watch 学西监听器的使用 watch( 1, 2 , 3) 
//    (1)需要监听的数据 value 写法普通监听， [value1, value2] 数据写法监听多个, () => value.text 函数写法区别监听
//    (2)函数 接收新值和老值, deep 后 老值与新值一样bug
//    (3)options 配置深度监听以及立即执行

let watchEffectValue1 = ref<string>('哈哈')
let watchEffectValue2 = ref<string>('哈哈')
const stop = watchEffect((oninvalidate) => { // watchEffect 监听高级，会立即监听。 可传一个回调函数oninvalidat
    // let ipt:HTMLInputElement = document.querySelector('#ipt') as HTMLInputElement
    // console.log(ipt)
    console.log('watchEffect1====>', watchEffectValue1.value)
    // console.log('watchEffect0====>', watchEffectValue2.value)
    oninvalidate(() => { // 会在监听改变之前触发
        console.log('before')
    })
}, { // 配置选项
    flush: 'post', // pre 组件跟新前，sync 强制效果同步触发，post 组件更新后
    onTrigger(e) { // onTrigger 触发调试
        debugger
    },
})
const stopWatchEffect = () => {
    stop() // watchEffect 返回一个stop函数停止监听 点击会立即触发 oninvalidate回调
}

let flag = ref(false)

const changeFlag = () => {
    flag.value = !flag.value
}

</script>

<style lang="less">

</style>
